/*
 * cpummu.h - MMU emulation
 *
 * Copyright (c) 2001-2004 Milan Jurik of ARAnyM dev team (see AUTHORS)
 *
 * Inspired by UAE MMU patch
 *
 * This file is part of the ARAnyM project which builds a new and powerful
 * TOS/FreeMiNT compatible virtual machine running on almost any hardware.
 *
 * ARAnyM is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * ARAnyM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ARAnyM; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#pragma once

#include "newcpu.h"
#include "readcpu.h"

#define FULLMMU

#define DUNUSED(x)
// #ifdef DEBUG
//     #define bug Logger::Write
// #else
//     #define bug
// #endif
struct m68k_exception
{
    int prb;
    m68k_exception (int exc) : prb(exc)
    {
    }
    operator int ()
    {
        return prb;
    }
};
#define SAVE_EXCEPTION
#define RESTORE_EXCEPTION
#define TRY_MMU(var) try
#define CATCH_MMU(var) catch (m68k_exception var)
#define THROW_MMU(n) throw m68k_exception(n)
#define THROW_AGAIN(var) throw
#define VOLATILE
#define ALWAYS_INLINE __inline

#define likely(x) x
#define unlikely(x) x
static __inline void flush_internals()
{
}

typedef byte flagtype;

struct xttrx
{
    uint log_addr_base : 8;
    uint log_addr_mask : 8;
    uint enable : 1;
    uint s_field : 2;
    uint : 3;
    uint usr1 : 1;
    uint usr0 : 1;
    uint : 1;
    uint cmode : 2;
    uint : 2;
    uint write : 1;
    uint : 2;
};

struct mmusr_t
{
    uint phys_addr : 20;
    uint bus_err : 1;
    uint global : 1;
    uint usr1 : 1;
    uint usr0 : 1;
    uint super : 1;
    uint cmode : 2;
    uint modif : 1;
    uint : 1;
    uint write : 1;
    uint ttrhit : 1;
    uint resident : 1;
};

struct log_addr4
{
    uint rif : 7;
    uint pif : 7;
    uint paif : 6;
    uint poff : 12;
};

struct log_addr8
{
    uint rif : 7;
    uint pif : 7;
    uint paif : 5;
    uint poff : 13;
};

#define MMU_TEST_PTEST 1
#define MMU_TEST_VERBOSE 2
#define MMU_TEST_FORCE_TABLE_SEARCH 4
#define MMU_TEST_NO_BUSERR 8

extern void mmu_dump_tables();

#define MMU_TTR_LOGICAL_BASE 0xff000000
#define MMU_TTR_LOGICAL_MASK 0x00ff0000
#define MMU_TTR_BIT_ENABLED (1 << 15)
#define MMU_TTR_BIT_SFIELD_ENABLED (1 << 14)
#define MMU_TTR_BIT_SFIELD_SUPER (1 << 13)
#define MMU_TTR_SFIELD_SHIFT 13
#define MMU_TTR_UX_MASK ((1 << 9) | (1 << 8))
#define MMU_TTR_UX_SHIFT 8
#define MMU_TTR_CACHE_MASK ((1 << 6) | (1 << 5))
#define MMU_TTR_CACHE_SHIFT 5
#define MMU_TTR_BIT_WRITE_PROTECT (1 << 2)

#define MMU_UDT_MASK 3
#define MMU_PDT_MASK 3

#define MMU_DES_WP 4
#define MMU_DES_USED 8

/* page descriptors only */
#define MMU_DES_MODIFIED 16
#define MMU_DES_SUPER (1 << 7)
#define MMU_DES_GLOBAL (1 << 10)

#define MMU_ROOT_PTR_ADDR_MASK 0xfffffe00
#define MMU_PTR_PAGE_ADDR_MASK_8 0xffffff80
#define MMU_PTR_PAGE_ADDR_MASK_4 0xffffff00

#define MMU_PAGE_INDIRECT_MASK 0xfffffffc
#define MMU_PAGE_ADDR_MASK_8 0xffffe000
#define MMU_PAGE_ADDR_MASK_4 0xfffff000
#define MMU_PAGE_UR_MASK_8 ((1 << 12) | (1 << 11))
#define MMU_PAGE_UR_MASK_4 (1 << 11)
#define MMU_PAGE_UR_SHIFT 11

#define MMU_MMUSR_ADDR_MASK 0xfffff000
#define MMU_MMUSR_B (1 << 11)
#define MMU_MMUSR_G (1 << 10)
#define MMU_MMUSR_U1 (1 << 9)
#define MMU_MMUSR_U0 (1 << 8)
#define MMU_MMUSR_Ux (MMU_MMUSR_U1 | MMU_MMUSR_U0)
#define MMU_MMUSR_S (1 << 7)
#define MMU_MMUSR_CM ((1 << 6) | (1 << 5))
#define MMU_MMUSR_M (1 << 4)
#define MMU_MMUSR_W (1 << 2)
#define MMU_MMUSR_T (1 << 1)
#define MMU_MMUSR_R (1 << 0)

/* special status word (access error stack frame) */
#define MMU_SSW_TM 0x0007
#define MMU_SSW_TT 0x0018
#define MMU_SSW_SIZE 0x0060
#define MMU_SSW_SIZE_B 0x0020
#define MMU_SSW_SIZE_W 0x0040
#define MMU_SSW_SIZE_L 0x0000
#define MMU_SSW_RW 0x0100
#define MMU_SSW_LK 0x0200
#define MMU_SSW_ATC 0x0400
#define MMU_SSW_MA 0x0800
#define MMU_SSW_CM 0x1000
#define MMU_SSW_CT 0x2000
#define MMU_SSW_CU 0x4000
#define MMU_SSW_CP 0x8000

#define TTR_I0 4
#define TTR_I1 5
#define TTR_D0 6
#define TTR_D1 7

#define TTR_NO_MATCH 0
#define TTR_NO_WRITE 1
#define TTR_OK_MATCH 2

struct mmu_atc_line
{
    ushort tag;
    unsigned tt : 1;
    unsigned valid_data : 1;
    unsigned valid_inst : 1;
    unsigned global : 1;
    unsigned modified : 1;
    unsigned write_protect : 1;
    unsigned hw : 1;
    unsigned bus_fault : 1;
    uae_ptr phys;
};

/*
 * We don't need to store the whole logical address in the atc cache, as part of
 * it is encoded as index into the cache. 14 bits of the address are stored in
 * the tag, this means at least 6 bits must go into the index. The upper two
 * bits of the tag define the type of data in the atc line:
 * - 00: a normal memory address
 * - 11: invalid memory address or hardware access
 *       (generated via ~ATC_TAG(addr) in the slow path)
 * - 10: empty atc line
 */

#define ATC_TAG_SHIFT 18
#define ATC_TAG(addr)       ((uint)(addr) >> ATC_TAG_SHIFT)

#define ATC_L1_SIZE_LOG 8
#define ATC_L1_SIZE (1 << ATC_L1_SIZE_LOG)

#define ATC_L1_INDEX(addr)  (((addr) >> 12) % ATC_L1_SIZE)

/*
 * first level atc cache
 * indexed by [super][data][rw][idx]
 */

typedef struct mmu_atc_line mmu_atc_l1_array[2][2][ATC_L1_SIZE];
extern mmu_atc_l1_array atc_l1[2];
extern mmu_atc_l1_array* current_atc;

#define ATC_L2_SIZE_LOG 12
#define ATC_L2_SIZE (1 << ATC_L2_SIZE_LOG)

#define ATC_L2_INDEX(addr)  ((((addr) >> 12) ^ ((addr) >> (32 - ATC_L2_SIZE_LOG))) % ATC_L2_SIZE)

// extern struct mmu_atc_line atc_l2[2][ATC_L2_SIZE];

/*
 * lookup address in the level 1 atc cache,
 * the data and write arguments are constant in the common,
 * thus allows gcc to generate a constant offset.
 */
static ALWAYS_INLINE bool mmu_lookup(uae_ptr addr, bool data, bool write,
    struct mmu_atc_line** cl)
{
    addr >>= 12;
    *cl = &(*current_atc)[data ? 1 : 0][write ? 1 : 0][addr % ATC_L1_SIZE];
    return (*cl)->tag == addr >> (ATC_TAG_SHIFT - 12);
}

/*
 * similiar to mmu_user_lookup, but for the use of the moves instruction
 */
static ALWAYS_INLINE bool mmu_user_lookup(uae_ptr addr, bool super, bool data,
    bool write, struct mmu_atc_line** cl)
{
    addr >>= 12;
    *cl = &atc_l1[super ? 1 : 0][data ? 1 : 0][write ? 1 : 0][addr % ATC_L1_SIZE];
    return (*cl)->tag == addr >> (ATC_TAG_SHIFT - 12);
}

extern ushort REGPARAM3 mmu_get_word_unaligned(uae_ptr addr, bool data) REGPARAM;
extern uint REGPARAM3 mmu_get_long_unaligned(uae_ptr addr, bool data) REGPARAM;

extern byte REGPARAM3 mmu_get_byte_slow(uae_ptr addr, bool super, bool data,
    int size, struct mmu_atc_line* cl) REGPARAM;
extern ushort REGPARAM3 mmu_get_word_slow(uae_ptr addr, bool super, bool data,
    int size, struct mmu_atc_line* cl) REGPARAM;
extern uint REGPARAM3 mmu_get_long_slow(uae_ptr addr, bool super, bool data,
    int size, struct mmu_atc_line* cl) REGPARAM;

extern void REGPARAM3 mmu_put_word_unaligned(uae_ptr addr, ushort val, bool data) REGPARAM;
extern void REGPARAM3 mmu_put_long_unaligned(uae_ptr addr, uint val, bool data) REGPARAM;

extern void REGPARAM3 mmu_put_byte_slow(uae_ptr addr, byte val, bool super, bool data,
    int size, struct mmu_atc_line* cl) REGPARAM;
extern void REGPARAM3 mmu_put_word_slow(uae_ptr addr, ushort val, bool super, bool data,
    int size, struct mmu_atc_line* cl) REGPARAM;
extern void REGPARAM3 mmu_put_long_slow(uae_ptr addr, uint val, bool super, bool data,
    int size, struct mmu_atc_line* cl) REGPARAM;

extern void mmu_make_transparent_region(uae_ptr baseaddr, uint size, int datamode);

#define FC_DATA (regs.s ? 5 : 1)
#define FC_INST (regs.s ? 6 : 2)

extern uae_ptr REGPARAM3 mmu_translate(uae_ptr addr, bool super, bool data, bool write) REGPARAM;

extern uint REGPARAM3 sfc_get_long(uae_ptr addr) REGPARAM;
extern ushort REGPARAM3 sfc_get_word(uae_ptr addr) REGPARAM;
extern byte REGPARAM3 sfc_get_byte(uae_ptr addr) REGPARAM;
extern void REGPARAM3 dfc_put_long(uae_ptr addr, uint val) REGPARAM;
extern void REGPARAM3 dfc_put_word(uae_ptr addr, ushort val) REGPARAM;
extern void REGPARAM3 dfc_put_byte(uae_ptr addr, byte val) REGPARAM;

extern void REGPARAM3 mmu_flush_atc(uae_ptr addr, bool super, bool global) REGPARAM;
extern void REGPARAM3 mmu_flush_atc_all(bool global) REGPARAM;
extern void REGPARAM3 mmu_op_real(uint opcode, ushort extra) REGPARAM;

extern void REGPARAM3 mmu_reset() REGPARAM;
extern void REGPARAM3 mmu_set_tc(ushort tc) REGPARAM;
extern void REGPARAM3 mmu_set_super(bool super) REGPARAM;

static ALWAYS_INLINE bool is_unaligned(uae_ptr addr, int size)
{
    return unlikely((addr & (size - 1)) && (addr ^ (addr + size - 1)) & 0x1000);
}

static ALWAYS_INLINE uae_ptr mmu_get_real_address(uae_ptr addr, struct mmu_atc_line* cl)
{
    return cl->phys + addr;
}

static ALWAYS_INLINE void phys_put_long(uae_ptr addr, uint l)
{
    longput(addr, l);
}
static ALWAYS_INLINE void phys_put_word(uae_ptr addr, uint w)
{
    wordput(addr, w);
}
static ALWAYS_INLINE void phys_put_byte(uae_ptr addr, uint b)
{
    byteput(addr, b);
}
static ALWAYS_INLINE uint phys_get_long(uae_ptr addr)
{
    return longget(addr);
}
static ALWAYS_INLINE uint phys_get_word(uae_ptr addr)
{
    return wordget(addr);
}
static ALWAYS_INLINE uint phys_get_byte(uae_ptr addr)
{
    return byteget(addr);
}

static ALWAYS_INLINE uint mmu_get_long(uae_ptr addr, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_lookup(addr, data, false, &cl)))
        return phys_get_long(mmu_get_real_address(addr, cl));
    return mmu_get_long_slow(addr, regs.s != 0, data, size, cl);
}

static ALWAYS_INLINE ushort mmu_get_word(uae_ptr addr, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_lookup(addr, data, false, &cl)))
        return phys_get_word(mmu_get_real_address(addr, cl));
    return mmu_get_word_slow(addr, regs.s != 0, data, size, cl);
}

static ALWAYS_INLINE byte mmu_get_byte(uae_ptr addr, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_lookup(addr, data, false, &cl)))
        return phys_get_byte(mmu_get_real_address(addr, cl));
    return mmu_get_byte_slow(addr, regs.s != 0, data, size, cl);
}

static ALWAYS_INLINE void mmu_put_long(uae_ptr addr, uint val, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_lookup(addr, data, true, &cl)))
        phys_put_long(mmu_get_real_address(addr, cl), val);
    else
        mmu_put_long_slow(addr, val, regs.s != 0, data, size, cl);
}

static ALWAYS_INLINE void mmu_put_word(uae_ptr addr, ushort val, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_lookup(addr, data, true, &cl)))
        phys_put_word(mmu_get_real_address(addr, cl), val);
    else
        mmu_put_word_slow(addr, val, regs.s != 0, data, size, cl);
}

static ALWAYS_INLINE void mmu_put_byte(uae_ptr addr, byte val, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_lookup(addr, data, true, &cl)))
        phys_put_byte(mmu_get_real_address(addr, cl), val);
    else
        mmu_put_byte_slow(addr, val, regs.s != 0, data, size, cl);
}

static ALWAYS_INLINE uint mmu_get_user_long(uae_ptr addr, bool super, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_user_lookup(addr, super, data, false, &cl)))
        return phys_get_long(mmu_get_real_address(addr, cl));
    return mmu_get_long_slow(addr, super, data, size, cl);
}

static ALWAYS_INLINE ushort mmu_get_user_word(uae_ptr addr, bool super, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_user_lookup(addr, super, data, false, &cl)))
        return phys_get_word(mmu_get_real_address(addr, cl));
    return mmu_get_word_slow(addr, super, data, size, cl);
}

static ALWAYS_INLINE byte mmu_get_user_byte(uae_ptr addr, bool super, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_user_lookup(addr, super, data, false, &cl)))
        return phys_get_byte(mmu_get_real_address(addr, cl));
    return mmu_get_byte_slow(addr, super, data, size, cl);
}

static ALWAYS_INLINE void mmu_put_user_long(uae_ptr addr, uint val, bool super, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_user_lookup(addr, super, data, true, &cl)))
        phys_put_long(mmu_get_real_address(addr, cl), val);
    else
        mmu_put_long_slow(addr, val, super, data, size, cl);
}

static ALWAYS_INLINE void mmu_put_user_word(uae_ptr addr, ushort val, bool super, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_user_lookup(addr, super, data, true, &cl)))
        phys_put_word(mmu_get_real_address(addr, cl), val);
    else
        mmu_put_word_slow(addr, val, super, data, size, cl);
}

static ALWAYS_INLINE void mmu_put_user_byte(uae_ptr addr, byte val, bool super, bool data, int size)
{
    struct mmu_atc_line* cl;

    if (likely(mmu_user_lookup(addr, super, data, true, &cl)))
        phys_put_byte(mmu_get_real_address(addr, cl), val);
    else
        mmu_put_byte_slow(addr, val, super, data, size, cl);
}

static ALWAYS_INLINE void HWput_l(uae_ptr addr, uint l)
{
    put_long(addr, l);
}
static ALWAYS_INLINE void HWput_w(uae_ptr addr, uint w)
{
    put_word(addr, w);
}
static ALWAYS_INLINE void HWput_b(uae_ptr addr, uint b)
{
    put_byte(addr, b);
}
static ALWAYS_INLINE uint HWget_l(uae_ptr addr)
{
    return get_long(addr);
}
static ALWAYS_INLINE uint HWget_w(uae_ptr addr)
{
    return get_word(addr);
}
static ALWAYS_INLINE uint HWget_b(uae_ptr addr)
{
    return get_byte(addr);
}

static ALWAYS_INLINE uint uae_mmu_get_ilong(uae_ptr addr)
{
    if (unlikely(is_unaligned(addr, 4)))
        return mmu_get_long_unaligned(addr, false);
    return mmu_get_long(addr, false, sz_long);
}
static ALWAYS_INLINE ushort uae_mmu_get_iword(uae_ptr addr)
{
    if (unlikely(is_unaligned(addr, 2)))
        return mmu_get_word_unaligned(addr, false);
    return mmu_get_word(addr, false, sz_word);
}
static ALWAYS_INLINE ushort uae_mmu_get_ibyte(uae_ptr addr)
{
    return mmu_get_byte(addr, false, sz_byte);
}
static ALWAYS_INLINE uint uae_mmu_get_long(uae_ptr addr)
{
    if (unlikely(is_unaligned(addr, 4)))
        return mmu_get_long_unaligned(addr, true);
    return mmu_get_long(addr, true, sz_long);
}
static ALWAYS_INLINE ushort uae_mmu_get_word(uae_ptr addr)
{
    if (unlikely(is_unaligned(addr, 2)))
        return mmu_get_word_unaligned(addr, true);
    return mmu_get_word(addr, true, sz_word);
}
static ALWAYS_INLINE byte uae_mmu_get_byte(uae_ptr addr)
{
    return mmu_get_byte(addr, true, sz_byte);
}
static ALWAYS_INLINE void uae_mmu_put_long(uae_ptr addr, uint val)
{
    if (unlikely(is_unaligned(addr, 4)))
        mmu_put_long_unaligned(addr, val, true);
    else
        mmu_put_long(addr, val, true, sz_long);
}
static ALWAYS_INLINE void uae_mmu_put_word(uae_ptr addr, ushort val)
{
    if (unlikely(is_unaligned(addr, 2)))
        mmu_put_word_unaligned(addr, val, true);
    else
        mmu_put_word(addr, val, true, sz_word);
}
static ALWAYS_INLINE void uae_mmu_put_byte(uae_ptr addr, byte val)
{
    mmu_put_byte(addr, val, true, sz_byte);
}

static __forceinline void put_byte_mmu(uae_ptr addr, uint v)
{
    uae_mmu_put_byte(addr, v);
}
static __forceinline void put_word_mmu(uae_ptr addr, uint v)
{
    uae_mmu_put_word(addr, v);
}
static __forceinline void put_long_mmu(uae_ptr addr, uint v)
{
    uae_mmu_put_long(addr, v);
}
static __forceinline uint get_byte_mmu(uae_ptr addr)
{
    return uae_mmu_get_byte(addr);
}
static __forceinline uint get_word_mmu(uae_ptr addr)
{
    return uae_mmu_get_word(addr);
}
static __forceinline uint get_long_mmu(uae_ptr addr)
{
    return uae_mmu_get_long(addr);
}
static __forceinline uint get_ibyte_mmu(int o)
{
    uint pc = m68k_getpc() + o;
    return uae_mmu_get_iword(pc);
}
static __forceinline uint get_iword_mmu(int o)
{
    uint pc = m68k_getpc() + o;
    return uae_mmu_get_iword(pc);
}
static __forceinline uint get_ilong_mmu(int o)
{
    uint pc = m68k_getpc() + o;
    return uae_mmu_get_ilong(pc);
}
static __forceinline uint next_iword_mmu()
{
    uint pc = m68k_getpc();
    m68k_incpci(2);
    return uae_mmu_get_iword(pc);
}
static __forceinline uint next_ilong_mmu()
{
    uint pc = m68k_getpc();
    m68k_incpci(4);
    return uae_mmu_get_ilong(pc);
}

extern void m68k_do_rts_mmu();
extern void m68k_do_rte_mmu(uae_ptr a7);
extern void m68k_do_bsr_mmu(uae_ptr oldpc, int offset);

struct mmufixup
{
    int reg;
    uint value;
};
extern struct mmufixup mmufixup[2];